import math
import PySide6
from PySide6 import QtCore
from PySide6.QtWidgets import QGraphicsItem
from PySide6.QtCore import QRectF, QSize, QObject, QPointF
from PySide6.QtGui import Qt, QPen, QPixmap, QVector2D, QVector3D, QTransform

class RotateCtrlItem(QGraphicsItem, QObject):

    rotateSignal = QtCore.Signal(float, bool)    # 旋转。信号对象.参数分别为：旋转角度、是否完成本次旋转

    '''构造函数，radius为图形项的半径'''
    def __init__(self, radius, parent = None):
        QGraphicsItem.__init__(self, parent)
        QObject.__init__(self, parent)
        self.__size = QSize(2 * radius, 2 * radius)  #图形项的大小
        self.__bgImage = QPixmap("./image/rotate.png")
        self.__startPos = None     # 鼠标按下时的位置
        self.__angle = 0.0         # 从鼠标按下开始时后的旋转角度
        self.setFlag(QGraphicsItem.ItemIsFocusable)   # 让图形项可获取焦点
        self.setFlag(QGraphicsItem.ItemIsSelectable)
        if(parent != None):
            self.rotateSignal.connect(parent.rotateItemSlot)


    ''' 重写 boundingRect 函数'''
    def boundingRect(self) -> PySide6.QtCore.QRectF:
        margin = 1   # 裕量，上下左右四个边界都留出一些裕量，方便绘制图形(特别是轮廓)
        return QRectF(0 - self.__size.width() / 2.0 - margin, 0 - self.__size.height() / 2.0 - margin, 
                      self.__size.width() + 2 * margin, self.__size.height() + 2 * margin)

    '''重写 paint 函数'''
    def paint(self, painter: PySide6.QtGui.QPainter, option: PySide6.QtWidgets.QStyleOptionGraphicsItem, widget) -> None:
        pen = QPen()
        pen.setWidth(1)
        pen.setColor(Qt.red)
        pen.setStyle(Qt.DashLine)  # 虚线
        painter.setPen(pen)
        # 绘制椭圆外形
        painter.drawEllipse(self.rect().x(), self.rect().y(), self.__size.width(), self.__size.height() )
        # 绘制背景图
        painter.drawPixmap(self.rect().x(), self.rect().y(), 
                           self.rect().width(), self.rect().height(), self.__bgImage)

    '''重写鼠标按下事件'''
    def mousePressEvent(self, event: PySide6.QtWidgets.QGraphicsSceneMouseEvent) -> None:
        if self.__startPos == None :
            self.__startPos = event.scenePos() # 刚开始按下，记录按下的位置

        return QGraphicsItem.mousePressEvent(self, event)

    '''
    重写鼠标移动事件。根据鼠标移动，旋转角度的计算方式如下：
    1、计算上一次鼠标移动和本次鼠标移动位置之间的角度。   
    2、计算旋转的方向。根据计算的角度和方向，计算真正的选中角度（顺时针
    为正，逆时针为负），为 QGraphicsItem 本身设置变换矩阵。那么如何计算角度和方向呢？？

    角度和方向的计算方式如下：
        1、通过向量的 点乘 ，计算角度。单位向量点乘的值，正好为角度的余弦。
        2、通过向量的 叉乘 ，计算旋转的方向。叉乘的结果为与这两个向量垂直的向量，
           可以通过Z轴结果判断，如果结果为正表示顺时针，结果为负表示逆时针。
    ————————参考———————— 
    原文链接：自定义QGraphicsItem实现平移、改变尺寸和旋转(https://blog.csdn.net/douzhq/article/details/105017924)
    '''
    def mouseMoveEvent(self, event: PySide6.QtWidgets.QGraphicsSceneMouseEvent) -> None:
        if self.__startPos == None :
            return QGraphicsItem.mouseMoveEvent(self, event)

        curPos = event.scenePos()   # 当前鼠标的坐标
        parentScenePos = QPointF(0, 0)   # 父图形项的坐标（场景坐标）
        if self.parentItem() != None :
            parentScenePos = self.parentItem().pos()

        # 获取并设置为单位向量
        startVec = QVector2D(parentScenePos.x() - self.__startPos.x(),
                             parentScenePos.y() - self.__startPos.y())
        startVec.normalize()
        endVec = QVector2D(parentScenePos.x() - curPos.x()  ,
                          parentScenePos.y() - curPos.y() )
        endVec.normalize()

        # 单位向量点乘，计算角度
        dotValue = QVector2D.dotProduct(startVec, endVec)
        if (dotValue > 1.0):
            dotValue = 1.0
        elif (dotValue < -1.0):
            dotValue = -1.0

        dotValue = math.acos(dotValue)
        if math.isnan(dotValue) :
            dotValue = 0.0

        # 获取角度
        self.__angle = dotValue * 1.0 / (math.pi / 180)

        # 向量叉乘获取方向
        crossValue = QVector3D.crossProduct( \
            QVector3D(startVec, 1.0), \
            QVector3D(endVec, 1.0))
        # print(crossValue.z(), "," , self.__angle)
        if (crossValue.z() < 0):
            self.__angle = -self.__angle
        # 设置变化矩阵
        self.rotateSignal.emit(self.__angle, False)   # 发送旋转角度信号，此时旋转还未完成
        return QGraphicsItem.mouseMoveEvent(self, event)

    '''重写鼠标释放事件'''
    def mouseReleaseEvent(self, event: PySide6.QtWidgets.QGraphicsSceneMouseEvent) -> None:
        self.__startPos = None # 松开鼠标，则图形项缩放完成
        self.rotateSignal.emit(self.__angle, True)   # 发送旋转角度信号，旋转操作已完成
        return QGraphicsItem.mouseReleaseEvent(self, event)

    '''获取图形项矩形，图形项被框在这个矩形范围内'''
    def rect(self) -> PySide6.QtCore.QRectF:
        return QRectF(0 - self.__size.width() / 2.0 , 0 - self.__size.height() / 2.0 , 
                      self.__size.width() , self.__size.height() )

    